﻿using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Windows.Threading;
using System.Xml.Linq;
using log4net;
using log4net.Config;
using Microsoft.Office.Tools;
using ThreeByte.ActiveDeck.AddIn.Config;
using ThreeByte.ActiveDeck.Config;
using ThreeByte.ActiveDeck.Controls;
using ThreeByte.ActiveDeck.Service;
using Office = Microsoft.Office.Core;
using PowerPoint = Microsoft.Office.Interop.PowerPoint;
using Excel = Microsoft.Office.Interop.Excel;
using ThreeByte.Logging;
using ThreeByte.ActiveDeck.Data.UserResponse;
using System.IO;
using ThreeByte.ActiveDeck.AddIn.Controls;
using ThreeByte.Media;
using ThreeByte.ActiveDeck.AddIn.Extensibility;
using System.Reflection;
using System.Diagnostics;

namespace ThreeByte.ActiveDeck
{
    public partial class ThisAddIn
    {
        public static ThisAddIn CurrentAddIn;

        private static ILog log = LogManager.GetLogger(typeof(ThisAddIn));
        private static readonly ILog exceptionLog = LogManager.GetLogger("BugLogger");

        private Dictionary<PowerPoint.Presentation, WPFControl> ConfigControls {
            get;
            set;
        }

        private Dictionary<PowerPoint.Presentation, PropertiesControl> PropertiesControls {
            get;
            set;
        }

        private Dictionary<CustomTaskPane, CoreConfig> TaskPaneConfig {
            get;
            set;
        }

        private List<ActiveMacro> Macros = new List<ActiveMacro>();

        private void ThisAddIn_Startup(object sender, System.EventArgs e)
        {
            CurrentAddIn = this;

            //Setup Logging Infrastructure
            //Check to see if there is a config file locally
            string configFile = GetType().Assembly.CodeBase + ".config";
            configFile = configFile.Replace("file:///", string.Empty);
            if(File.Exists(configFile)) {
                XmlConfigurator.Configure(new FileInfo(configFile));
                log.InfoFormat("Configuration loaded locally: {0}", configFile);
            } else {
                //If not, then look for one in the Program Files directory
                string assemblyName = GetType().Assembly.ManifestModule.Name;
                configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
                                                                     "Three Byte Intermedia",
                                                                     "ActiveDeck",
                                                                     assemblyName + ".config");
                if(File.Exists(configFile)) {
                    XmlConfigurator.Configure(new FileInfo(configFile));
                    log.InfoFormat("Configuration loaded from installation: {0}", configFile);
                } else {
                    //If it is still not there, just call configure.
                    XmlConfigurator.Configure();
                    log.Info("Configuration loaded automagically");
                }
            }

            //#6084 Add AppDomain Handler
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

            ConfigControls = new Dictionary<PowerPoint.Presentation, WPFControl>();
            TaskPaneConfig = new Dictionary<CustomTaskPane, CoreConfig>();
            PropertiesControls = new Dictionary<PowerPoint.Presentation, PropertiesControl>();

            Application.SlideSelectionChanged += new PowerPoint.EApplication_SlideSelectionChangedEventHandler(Application_SlideSelectionChanged);
            //Application.PresentationBeforeSave += new PowerPoint.EApplication_PresentationBeforeSaveEventHandler(Application_PresentationBeforeSave);
            Application.PresentationSave += new PowerPoint.EApplication_PresentationSaveEventHandler(Application_PresentationSave);
            //Application.PresentationOpen += new PowerPoint.EApplication_PresentationOpenEventHandler(Application_PresentationOpen);
            Application.AfterNewPresentation += new PowerPoint.EApplication_AfterNewPresentationEventHandler(Application_AfterNewPresentation);
            Application.AfterPresentationOpen += new PowerPoint.EApplication_AfterPresentationOpenEventHandler(Application_AfterPresentationOpen);
            Application.PresentationClose += new PowerPoint.EApplication_PresentationCloseEventHandler(Application_PresentationClose);
            Application.PresentationBeforeClose += new PowerPoint.EApplication_PresentationBeforeCloseEventHandler(Application_PresentationBeforeClose);

            //Generally for updating userresponses and graphics
            Application.WindowSelectionChange += new PowerPoint.EApplication_WindowSelectionChangeEventHandler(Application_WindowSelectionChange);

            Application.SlideShowBegin += new PowerPoint.EApplication_SlideShowBeginEventHandler(Application_SlideShowBegin);
            Application.SlideShowEnd += new PowerPoint.EApplication_SlideShowEndEventHandler(Application_SlideShowEnd);

            //Initialize GraphicResource Storage
            GraphicResourceWrapper.GetBytes = new GraphicResourceWrapper.GetBytesDelegate(GetGraphicResourceBytes);

            LoadMacros();
        }

        private void LoadMacros() {
            //Look for any assemblies with ActiveMacro classes
            string searchFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
                                                                         "ActiveDeck",
                                                                         "Macros");
            //Create if the folder does not exist
            if(!Directory.Exists(searchFolder)) {
                Directory.CreateDirectory(searchFolder);
            }
            string[] foundFiles = Directory.GetFiles(searchFolder, "*.dll");
            foreach(string s in foundFiles) {
                log.DebugFormat("Loading macro file: {0}", s);

                try {
                    Assembly macroAssembly = Assembly.LoadFrom(s);
                    foreach(Type t in macroAssembly.GetTypes()) {
                        //Look for macro-attributed classes
                        if(t.GetCustomAttributes(typeof(ActiveMacroAttribute), true).Length > 0) {
                            ConstructorInfo constructor = t.GetConstructor(new Type[]{});
                            ActiveMacro newMacro = (ActiveMacro)constructor.Invoke(new object[] { });
                            Macros.Add(newMacro);
                        }
                    }

                } catch(Exception ex) {
                    log.Error("Loading macro failed", ex);
                }
            }

            RibbonTab thisRibbon = Globals.Ribbons.RibbonTab;
            Microsoft.Office.Tools.Ribbon.RibbonGroup macroGroup = thisRibbon.Factory.CreateRibbonGroup();
            thisRibbon.tab1.Groups.Add(macroGroup);
            macroGroup.Label = "Macros";
            macroGroup.Name = "MacroGroup";

            foreach(ActiveMacro m in Macros) {
                //Create a new button
                Microsoft.Office.Tools.Ribbon.RibbonButton newButton =  thisRibbon.Factory.CreateRibbonButton();
                newButton.Label = m.DisplayName;
                newButton.Click += new Microsoft.Office.Tools.Ribbon.RibbonControlEventHandler(newButton_Click);
                newButton.Tag = m;
                macroGroup.Items.Add(newButton);

            }
        }

        void newButton_Click(object sender, Microsoft.Office.Tools.Ribbon.RibbonControlEventArgs e) {
            Microsoft.Office.Tools.Ribbon.RibbonButton thisButton = (Microsoft.Office.Tools.Ribbon.RibbonButton)sender;
            ActiveMacro theMacro = (ActiveMacro)(thisButton.Tag);
            try {
                theMacro.Execute();
            } catch(Exception ex) {
                log.Error("Macro execution failed", ex);
            }
        }


        private byte[] GetGraphicResourceBytes(object storageRoot, Guid id) {
            GraphicResourceSerializer serializer = (GraphicResourceSerializer)storageRoot;
            return serializer.GetBytesForResource(id);
        }


        void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) {
            DialogResult result = MessageBox.Show("We are very sorry but the application cannot continue.  Please click OK to automatically report this issue.  Thanks.", "Bug Report",
                                                    MessageBoxButtons.OKCancel, MessageBoxIcon.Asterisk);
            if(result == DialogResult.OK) {
                string errorMessage = "Project: activedeck\nStatus: New\nPriority: High\nTracker: Bug\n\n";
                errorMessage += LogUtil.GetDiagnosticString() + "\n\n";
                errorMessage += LogUtil.GetRecentEventString();
                exceptionLog.Fatal(errorMessage, e.ExceptionObject as Exception);

                errorMessage += string.Format("{0}", e.ExceptionObject as Exception);
                errorMessage += string.Format("{0}", ((Exception)(e.ExceptionObject)).Message);

                //Open a dialog to choose where to write the log file to:
                SaveFileDialog saveFile = new SaveFileDialog();
                DialogResult saveResult = saveFile.ShowDialog();
                if(saveResult == DialogResult.OK) {
                    File.WriteAllText(saveFile.FileName, errorMessage);
                }
            }
        }


        void Application_WindowSelectionChange(PowerPoint.Selection Sel) {
            log.DebugFormat("The selection type is: {0}", Sel.Type);
            Globals.Ribbons.RibbonTab.ShowInteractionGroup(null);

            PowerPoint.Presentation pres = Application.ActivePresentation;
            if(!(ConfigControls.ContainsKey(pres))) {
                return;
            }

            if(Sel.Type != PowerPoint.PpSelectionType.ppSelectionShapes) {
                //GetPropertiesControl().Hide();
                return;
            }

            if(Sel.ShapeRange.Count == 1) {
                
                PowerPoint.Shape s = Sel.ShapeRange[1];
                GetPropertiesControl().Show(s);
                    log.DebugFormat("Shape Type: {0}", s.Type);
                    Globals.Ribbons.RibbonTab.ShowInteractionGroup(s);
                    if (s.Type == Office.MsoShapeType.msoLinkedOLEObject) {
                        log.DebugFormat("ProgID: {0} / {1}", s.OLEFormat.ProgID, s.LinkFormat.SourceFullName);
                    }
            }

        }

        void Application_PresentationBeforeClose(PowerPoint.Presentation Pres, ref bool Cancel) {

            log.Debug("Presentation BeforeClose");
            //Check to see if the presentation is dirty?
            if(Pres.Saved != Office.MsoTriState.msoTrue) {
               DialogResult result = MessageBox.Show("There are unsaved changes to your presentation.  Would you like to save?", "Save changes?", MessageBoxButtons.YesNoCancel);
               if(result == DialogResult.Yes) {
                   Pres.Save();
               } else if(result == DialogResult.No) {
                   Pres.Saved = Office.MsoTriState.msoTrue;
               } else if(result == DialogResult.Cancel) {
                   Cancel = true;
               }
            }
        }

        void Application_PresentationClose(PowerPoint.Presentation Pres) {
            log.Debug("Presentation Close");

            //Deconstruct the config for this presentation
            CollapseConfig(Pres);
        }

        

        void Application_SlideShowEnd(PowerPoint.Presentation Pres) {
            if(PresentationMonitors.ContainsKey(Pres)) {
                PresentationMonitor presMonitor = PresentationMonitors[Pres];
                presMonitor.Dispose();
                PresentationMonitors.Remove(Pres);
            }
        }

        void Application_SlideShowBegin(PowerPoint.SlideShowWindow Wn) {
            log.Info("Starting SlideShow");

            //Get the configuration from the current slide, (if one exists)
            if(!(ConfigControls.ContainsKey(Wn.Presentation))) {
                return;  //Short circuit and don't interfere with presentations that don't have anything configured
            }
            
            //Create and store a new monitoring object
            CoreConfig currentCoreConfig = ConfigControls[Wn.Presentation].Config;


            //Make sure there is some interactivity that we care about
            bool containsSlides = false;
            foreach(SlideConfig s in currentCoreConfig.Slides) {
                containsSlides = true;
                break;
            }
            if(!containsSlides) {
                return; //No interactivity
            }

            if(!(currentCoreConfig.HasPresentationID)) {
                //No presentation ID assigned
                MessageBox.Show("This presentation contains interactivity but has not been published yet.  Please publish this presentation to enable interactivity.",
                    "Presentation not published");
                return;
            }

            PresentationMonitor newPresentationMonitor = new PresentationMonitor(currentCoreConfig, Wn, Dispatcher.CurrentDispatcher);
            PresentationMonitors[Wn.Presentation] = newPresentationMonitor;

            //TODO: Test if the Op monitor is enabled
            if(currentCoreConfig.Environment.ShowInteractivePresentationMonitor) {
                OperatorMonitorForm newOpMonitorForm = new OperatorMonitorForm(newPresentationMonitor);
                newOpMonitorForm.Show();
            }

        }

        private Dictionary<PowerPoint.Presentation, PresentationMonitor> PresentationMonitors = new Dictionary<PowerPoint.Presentation, PresentationMonitor>();

        void Application_AfterPresentationOpen(PowerPoint.Presentation pres) {
            XNamespace ns = ServiceConfiguration.NAMESPACE;

            Office.CustomXMLParts parts = pres.CustomXMLParts.SelectByNamespace(ns.NamespaceName);
            
            XElement presElement = null;
            Dictionary<Guid, GraphicResource> resources = new Dictionary<Guid, GraphicResource>();
            GraphicResourceCustomXMLLookup[pres] = new List<string>();
            GraphicResourceIDMappingLookup[pres] = new Dictionary<Guid, string>();
            bool hasExternalGraphics = false;
            foreach(Office.CustomXMLPart p in parts) {
                XElement newElement = XElement.Parse(p.XML);
                if(newElement.Name == (ns + "Config")) {
                    presElement = newElement;
                    CustomXMLLookup[pres] = p.Id;
                } else if(newElement.Name == GraphicResourceSerializer.EXTERNAL_GRAPHICS_FLAG.Name){
                    hasExternalGraphics = true;
                    ExternalGraphicCustomXMLLookup[pres] = p.Id;
                } else if(newElement.Name == (ns + "Graphic")) {
                    //Load internal graphics - Parse the resource
                    GraphicResource newResource = GraphicResource.FromXml(newElement);
                    resources[newResource.ID] = newResource;
                    log.DebugFormat("Add Graphic Guid: {0} [{1}]", newResource.ID, p.Id);
                    GraphicResourceCustomXMLLookup[pres].Add(p.Id);
                    //Map Graphic ResourceID to the persistent CustomXMl
                    GraphicResourceIDMappingLookup[pres][newResource.ID] = p.Id;
                }
            }

            if(hasExternalGraphics) {
                //Ignores any existing internal graphics, which will get purged when the presentation is saved.
                string storepath = string.Format("{0}.gfx", pres.FullName);
                if(!Directory.Exists(storepath)) {
                    MessageBox.Show(string.Format("The expected graphics directory cannot be found: {0}\nYour graphics will not be loaded.  Please restore the appropriate directory and reopen the file before making any changes.", storepath));
                    presElement = null; //Convice the system the ActiveDeck config does not exist.
                } else {
                    GraphicResourceSerializerLookup[pres] = new GraphicResourceSerializer(storepath);
                    resources = GraphicResourceSerializerLookup[pres].LoadAllResources();
                }
            }

            if(presElement == null) {
                SetupTaskPane(new CoreConfig(pres));
            } else {
                CoreConfig config = CoreConfig.FromXML(pres, presElement, resources);
                config.StoreGraphicsSeparately = hasExternalGraphics;
                SetupTaskPane(config);
                //One time initialization for files that have existing content
                //Possibly defer this work to a separate process with a loading indicator
                //foreach(SlideConfig s in config.Slides) {
                //    foreach(PageConfig p in s.Pages) {
                //        if(p is DesignerSlideConfig) {
                //            foreach(LayerConfig l in ((DesignerSlideConfig)p).Layers) {
                //                l.Refresh();
                //            }
                //        }
                //        if(p is NavigatorConfig) {
                //            foreach(LayerConfig l in ((NavigatorConfig)p).Layers) {
                //                l.Refresh();
                //            }
                //        }
                //    }
                //}
            }

        }

        void Application_AfterNewPresentation(PowerPoint.Presentation pres) {
            SetupTaskPane(new CoreConfig(pres));
        }

        public void ShowActiveConfigurationTaskPane(bool visible) {
            PowerPoint.Presentation pres = Application.ActivePresentation;
            if(pres != null) {
                if(ConfigControls.ContainsKey(pres)) {
                    ConfigControls[pres].TaskPane.Visible = visible;
                }
            }
        }

        public CoreConfig GetConfig(PowerPoint.Presentation pres) {
            return ConfigControls[pres].Config;
        }

        public CoreConfig GetCurrentConfig() {
            PowerPoint.Presentation pres = Application.ActivePresentation;
            return GetConfig(pres);
        }

        //TODO: This should be refactored, we need an independent call because the current config does not
        //exist in the cached before all its depenedencies have been constructed.
        public string GetCurrentPresentationPath() {
            PowerPoint.Presentation pres = Application.ActivePresentation;
            return pres.Path;
        }

        public GraphicResourceSerializer GetCurrentResourceSerializer() {
            PowerPoint.Presentation pres = Application.ActivePresentation;
            if(GraphicResourceSerializerLookup.ContainsKey(pres)) {
                return GraphicResourceSerializerLookup[pres];
            } else {
                return null;
            }
        }

        public PropertiesControl GetPropertiesControl() {
            PowerPoint.Presentation pres = Application.ActivePresentation;
            return PropertiesControls[pres];
        }


        //Maps presentations to the internal CustomXML identifier used in the .pptx file format
        private Dictionary<PowerPoint.Presentation, string> CustomXMLLookup = new Dictionary<PowerPoint.Presentation, string>();
        private Dictionary<PowerPoint.Presentation, string> ExternalGraphicCustomXMLLookup = new Dictionary<PowerPoint.Presentation, string>();
        
        //Maps presentations to a list of CustomXML identifiers associated with persistent graphic resources
        private Dictionary<PowerPoint.Presentation, List<string>> GraphicResourceCustomXMLLookup = new Dictionary<PowerPoint.Presentation, List<string>>();

        //Maps presentations to a dictionary of GraphicResource IDs to CustomXML identifiers
        private Dictionary<PowerPoint.Presentation, Dictionary<Guid, string>> GraphicResourceIDMappingLookup = new Dictionary<PowerPoint.Presentation, Dictionary<Guid, string>>();

        private Dictionary<PowerPoint.Presentation, GraphicResourceSerializer> GraphicResourceSerializerLookup = new Dictionary<PowerPoint.Presentation, GraphicResourceSerializer>();

        void Application_PresentationSave(PowerPoint.Presentation Pres) {
            log.Debug("Saving Presentation");

            //Is it ok to ignore this is the presentation doesn't exist anymore?
            if(!(ConfigControls.ContainsKey(Pres))) {
                return;
            }
            CoreConfig config = ConfigControls[Pres].Config;
            config.PruneUnusedSlides();
            config.PruneUnusedGraphics();

            //External Serializer
            if(!(GraphicResourceSerializerLookup.ContainsKey(Pres))) {
                string storepath = string.Format("{0}.gfx", Pres.FullName);
                GraphicResourceSerializerLookup[Pres] = new GraphicResourceSerializer(storepath);
            }

            //Flag for storing external graphics
            if(ExternalGraphicCustomXMLLookup.ContainsKey(Pres)) {
                Pres.CustomXMLParts.SelectByID(ExternalGraphicCustomXMLLookup[Pres]).Delete();
                ExternalGraphicCustomXMLLookup.Remove(Pres);
            }

            if(config.StoreGraphicsSeparately) {
                SerializeAllResourcesExternally(config, Pres);
                //Purge all custom XML resources that are no longer necessary
                PuregeAllCustomXMLGraphics(config, Pres);

                //Store flag for external graphics
                Office.CustomXMLPart xml = Pres.CustomXMLParts.Add(GraphicResourceSerializer.EXTERNAL_GRAPHICS_FLAG.ToString());
                ExternalGraphicCustomXMLLookup[Pres] = xml.Id;
            } else {
                SerializeAllResources(config, Pres);
                //Ignore anything that was already stored externally
            }

            string currentConfigString = config.ToXML();

            //Presentation Identifier
            if (CustomXMLLookup.ContainsKey(Pres)) {
                Pres.CustomXMLParts.SelectByID(CustomXMLLookup[Pres]).Delete();
                CustomXMLLookup.Remove(Pres);
            }
            if(!(string.IsNullOrWhiteSpace(currentConfigString))){
                Office.CustomXMLPart xml = Pres.CustomXMLParts.Add(currentConfigString);
                CustomXMLLookup[Pres] = xml.Id;
            }

            

            Pres.Save();
        }

        private void SerializeAllResources(CoreConfig config, PowerPoint.Presentation pres) {

            Dictionary<Guid, GraphicResource> persistentResources = new Dictionary<Guid, GraphicResource>();

            //Iterate over all of the presentation level resources
            foreach(GraphicResource res in config.Graphics){
                persistentResources[res.ID] = res;
            }
            if (config.Logo != null) {
                persistentResources[config.Logo.ID] = config.Logo;
            }
            if(config.HomePage != null) {
                persistentResources[config.HomePage.ID] = config.HomePage;
            }
            if(config.EndPage != null) {
                persistentResources[config.EndPage.ID] = config.EndPage;
            }

            //Iterate over all of the slides and grab their persistent resources
            foreach(int id in config.SlideIDs) {
                foreach(PageConfig page in config[id].Pages) {
                    foreach(GraphicResource g in page.GetPersistentGraphicResources()) {
                        persistentResources[g.ID] = g;
                    }
                }
            }


            if(!(GraphicResourceCustomXMLLookup.ContainsKey(pres))) {
                GraphicResourceCustomXMLLookup[pres] = new List<string>();
            }

            //Find the list of persistent custom XML resources that need to be deleted
            List<string> customXMLIdsToBeDeleted = new List<string>(GraphicResourceCustomXMLLookup[pres]);

            if(!(GraphicResourceIDMappingLookup.ContainsKey(pres))) {
                GraphicResourceIDMappingLookup[pres] = new Dictionary<Guid, string>();
            }

            //The list of Graphic resources that don't have custom XML ids yet
            //List<Guid> graphicResourcesToBeSerialized = new List<Guid>();
            log.DebugFormat("Persistent Resources to Serialize: {0}", persistentResources.Count);

            int resourcesAddedCount = 0;
            try {
                foreach(Guid g in persistentResources.Keys) {
                    if(customXMLIdsToBeDeleted.Count == 0) {
                        log.DebugFormat("No more CustomXML to consider.  Short-circuit");
                        //break; //Nothing left to check for
                    }
                    if(GraphicResourceIDMappingLookup[pres].ContainsKey(g)) {
                        //This resource was previously mapped, and still exists, it should not be deleted
                        customXMLIdsToBeDeleted.Remove(GraphicResourceIDMappingLookup[pres][g]);
                    } else {
                        //This resource was not previously mapped, so we need to add it to the map
                        //graphicResourcesToBeSerialized.Add(g);
                        log.DebugFormat("GraphicResource to be added: {0}", g);
                        resourcesAddedCount++;
                        //Serialize them and add them to the map.
                        string resourceBytes = persistentResources[g].ToXml().ToString();
                        Office.CustomXMLPart xml = pres.CustomXMLParts.Add(resourceBytes);
                        log.DebugFormat("Assign new Graphic Guid: {0} [{1}]", g, xml.Id);
                        GraphicResourceCustomXMLLookup[pres].Add(xml.Id);
                        GraphicResourceIDMappingLookup[pres][g] = xml.Id;
                    }
                }
            } catch(Exception ex) {
                log.ErrorFormat("Error inventorying resources", ex);
            }

            log.Debug("Done inventorying graphic resources");
            log.DebugFormat("Added {0} new Graphic resources", resourcesAddedCount);
            log.DebugFormat("Deleting {0} CustomXml Resources", customXMLIdsToBeDeleted.Count);
            log.DebugFormat("Graphic resources in ID Mapping: {0}", GraphicResourceIDMappingLookup[pres].Count);
            //Delete the unused resources and remove their Ids from the map
            foreach(string id in customXMLIdsToBeDeleted) {
                
                System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
                Office.CustomXMLPart partToDelete = pres.CustomXMLParts.SelectByID(id);
                long selectTime = sw.ElapsedMilliseconds;
                partToDelete.Delete();
                log.DebugFormat("GraphicResource (CustomXML) deleted: {0} in {1}/{2} ms", id, selectTime, sw.ElapsedMilliseconds);
                
                //Remove the unused graphic resource IDs....Reverse lookup
                //log.DebugFormat("***Need to remove: {0}", id);
                GraphicResourceCustomXMLLookup[pres].Remove(id); //Remove from the Cache of custom XML IDs
                //Remove from the GraphicResourceID mapping
                Guid graphicResourceIDToRemove = default(Guid);
                foreach(Guid g in GraphicResourceIDMappingLookup[pres].Keys) {
                    if(GraphicResourceIDMappingLookup[pres][g] == id) {
                        //Found the GraphicResource ID to remove
                        graphicResourceIDToRemove = g;
                        break;
                    }
                }
                if(graphicResourceIDToRemove != default(Guid)) {
                    GraphicResourceIDMappingLookup[pres].Remove(graphicResourceIDToRemove);
                }
            }
            
        }

        private void PuregeAllCustomXMLGraphics(CoreConfig config, PowerPoint.Presentation pres) {
            
            if(!(GraphicResourceCustomXMLLookup.ContainsKey(pres))) {
                return;  //Nothing to do
            }

            //Find the list of persistent custom XML resources that need to be deleted
            List<string> customXMLIdsToBeDeleted = new List<string>(GraphicResourceCustomXMLLookup[pres]);

            log.DebugFormat("Deleting {0} CustomXml Resources", customXMLIdsToBeDeleted.Count);
            log.DebugFormat("Graphic resources in ID Mapping: {0}", GraphicResourceIDMappingLookup[pres].Count);
            //Delete the unused resources and remove their Ids from the map
            foreach(string id in customXMLIdsToBeDeleted) {

                System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
                Office.CustomXMLPart partToDelete = pres.CustomXMLParts.SelectByID(id);
                long selectTime = sw.ElapsedMilliseconds;
                partToDelete.Delete();
                log.DebugFormat("GraphicResource (CustomXML) deleted: {0} in {1}/{2} ms", id, selectTime, sw.ElapsedMilliseconds);

                //Remove the unused graphic resource IDs....Reverse lookup
                GraphicResourceCustomXMLLookup[pres].Remove(id); //Remove from the Cache of custom XML IDs
                //Remove from the GraphicResourceID mapping
                Guid graphicResourceIDToRemove = default(Guid);
                foreach(Guid g in GraphicResourceIDMappingLookup[pres].Keys) {
                    if(GraphicResourceIDMappingLookup[pres][g] == id) {
                        //Found the GraphicResource ID to remove
                        graphicResourceIDToRemove = g;
                        break;
                    }
                }
                if(graphicResourceIDToRemove != default(Guid)) {
                    GraphicResourceIDMappingLookup[pres].Remove(graphicResourceIDToRemove);
                }
            }
        }


        private void SerializeAllResourcesExternally(CoreConfig config, PowerPoint.Presentation pres) {

            string storepath = GraphicResourceSerializerLookup[pres].StorePath;
            if(!Directory.Exists(storepath)) {
                try {
                    Directory.CreateDirectory(storepath);
                } catch(Exception ex) {
                    log.Debug("Cannot create graphics directory", ex);
                    MessageBox.Show(string.Format("Cannot create necessary graphics directory: {0}.\nYour presentation will not be saved correctly.", storepath));
                    return;
                }
            }

            Stopwatch sw = Stopwatch.StartNew();
            GraphicResourceSerializerLookup[pres].SerializeAllResources(config); 
            GraphicResourceSerializerLookup[pres].PurgeUnindexedFiles();

            log.DebugFormat("Done serializing files: {0}ms", sw.ElapsedMilliseconds);
        }

        void SetupTaskPane(CoreConfig config) {

            //Current Active Window
            PowerPoint.DocumentWindow docWindow = this.Application.ActiveWindow;
            PowerPoint.Presentation pres = docWindow.Presentation;
            SetupPropertiesPane(pres, config.Environment.ShowPropertiesPane);
            
            //Handle enabling/disabling the properties pane
            config.Environment.PropertiesPaneEnabled += new EventHandler(Environment_PropertiesPaneEnabled);
            config.Environment.PropertiesPaneDisabled += new EventHandler(Environment_PropertiesPaneDisabled);

            System.Windows.Forms.UserControl control = new System.Windows.Forms.UserControl();
            System.Windows.Forms.Integration.ElementHost host = new System.Windows.Forms.Integration.ElementHost();
            ConfigControls[pres] = new WPFControl(config);
            host.Child = ConfigControls[pres];
            
            control.Controls.Add(host);
            host.Dock = System.Windows.Forms.DockStyle.Fill;

            CustomTaskPane mainTaskPane = this.CustomTaskPanes.Add(control, "ActiveDeck", docWindow);
            ConfigControls[pres].TaskPane = mainTaskPane;

            TaskPaneConfig[mainTaskPane] = config;
            mainTaskPane.Visible = config.Environment.ShowTaskPane;
            mainTaskPane.VisibleChanged += new EventHandler(TaskPane_VisibleChanged);

            //Restore the User setting sizes for Task Pane
            if(ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.TaskPaneDockPosition >= 0) {
                mainTaskPane.DockPosition = (Office.MsoCTPDockPosition)(ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.TaskPaneDockPosition);
            }

            if((mainTaskPane.DockPosition == Office.MsoCTPDockPosition.msoCTPDockPositionLeft ||
                mainTaskPane.DockPosition == Office.MsoCTPDockPosition.msoCTPDockPositionRight ||
                mainTaskPane.DockPosition == Office.MsoCTPDockPosition.msoCTPDockPositionFloating) && 
                (ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.TaskPaneWidth != 0)) {
                    mainTaskPane.Width = ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.TaskPaneWidth;
            }
            if((mainTaskPane.DockPosition == Office.MsoCTPDockPosition.msoCTPDockPositionBottom ||
                mainTaskPane.DockPosition == Office.MsoCTPDockPosition.msoCTPDockPositionTop ||
                mainTaskPane.DockPosition == Office.MsoCTPDockPosition.msoCTPDockPositionFloating) &&
                (ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.TaskPaneHeight != 0)) {
                    mainTaskPane.Height = ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.TaskPaneHeight;
            }

            
            control.Tag = mainTaskPane;
            control.SizeChanged += new EventHandler(control_SizeChanged);
            mainTaskPane.DockPositionChanged += new EventHandler(ThisAddIn_DockPositionChanged);

            PowerPoint.SlideRange slideRange = docWindow.Selection.SlideRange;
            Application_SlideSelectionChanged(slideRange);

        }

        void Environment_PropertiesPaneDisabled(object sender, EventArgs e) {
            try {
                GetPropertiesControl().Hide();
            } catch(Exception ex) {
                log.Error("Cannot hide properties pane", ex);
            }
        }

        void Environment_PropertiesPaneEnabled(object sender, EventArgs e) {
            try {
                GetPropertiesControl().Show();
            } catch(Exception ex) {
                log.Error("Cannot show properties pane", ex);
            }
        }

        public void OpenWizard() {
            Microsoft.Office.Interop.PowerPoint.Presentation pres = Globals.ThisAddIn.Application.ActivePresentation;
            CoreConfig config = Globals.ThisAddIn.GetConfig(pres);
            ThreeByte.ActiveDeck.AddIn.Controls.Wizard.WizardMainFrame frame =
                new ThreeByte.ActiveDeck.AddIn.Controls.Wizard.WizardMainFrame(config);
            WizardForm wizForm = new WizardForm(frame);

            wizForm.ShowDialog();
        }

        void SetupPropertiesPane(PowerPoint.Presentation presentation, bool visible) {
            log.DebugFormat("Setup Properties: {0}", presentation.Name);

            //Current Active Window
            PowerPoint.DocumentWindow docWindow = this.Application.ActiveWindow;
            //PowerPoint.Presentation pres = docWindow.Presentation;

            System.Windows.Forms.UserControl control = new System.Windows.Forms.UserControl();
            System.Windows.Forms.Integration.ElementHost host = new System.Windows.Forms.Integration.ElementHost();
            PropertiesControls[presentation] = new PropertiesControl();
            host.Child = PropertiesControls[presentation];

            control.Controls.Add(host);
            host.Dock = System.Windows.Forms.DockStyle.Fill;

            PropertiesControls[presentation].TaskPane = this.CustomTaskPanes.Add(control, "Properties", docWindow);
            PropertiesControls[presentation].TaskPane.Visible = visible;
            PropertiesControls[presentation].TaskPane.VisibleChanged +=new EventHandler(PropertiesPane_VisibleChanged);

            //The properties control should generally float
            PropertiesControls[presentation].TaskPane.DockPosition = Office.MsoCTPDockPosition.msoCTPDockPositionFloating;
            PropertiesControls[presentation].TaskPane.DockPositionRestrict = Office.MsoCTPDockPositionRestrict.msoCTPDockPositionRestrictNoHorizontal | Office.MsoCTPDockPositionRestrict.msoCTPDockPositionRestrictNoVertical;

            if (ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.PropertiesPaneWidth != 0)
                PropertiesControls[presentation].TaskPane.Width = ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.PropertiesPaneWidth;

            if (ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.PropertiesPaneHeight != 0)
                PropertiesControls[presentation].TaskPane.Height = ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.PropertiesPaneHeight;

            //control.Location = ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.PropertiesPanePosition;

            control.SizeChanged += new EventHandler(propControl_SizeChanged);
        }


        void CollapseConfig(PowerPoint.Presentation pres) {
            log.DebugFormat("Collapsing Configuration: {0}", pres.Name);

            //Destroy the properties pane, remove reference
            PropertiesControls[pres].TaskPane.Visible = false;
            CustomTaskPanes.Remove(PropertiesControls[pres].TaskPane);
            PropertiesControls[pres].TaskPane.Dispose();
            PropertiesControls.Remove(pres);

            //Destroy the setup pane, remove reference
            ConfigControls[pres].TaskPane.VisibleChanged -= TaskPane_VisibleChanged;
            ConfigControls[pres].TaskPane.Visible = false;
            TaskPaneConfig.Remove(ConfigControls[pres].TaskPane);
            CustomTaskPanes.Remove(ConfigControls[pres].TaskPane);
            ConfigControls[pres].TaskPane.Dispose();
            ConfigControls.Remove(pres);

            //Remove presentation monitor - only if 
            if(PresentationMonitors.ContainsKey(pres)) {
                PresentationMonitors[pres].Dispose();
                PresentationMonitors.Remove(pres);
            }

            //Remove these:
            CustomXMLLookup.Remove(pres);
            GraphicResourceCustomXMLLookup.Remove(pres);
            GraphicResourceIDMappingLookup.Remove(pres);

            LayerConfig.ClearLayerCache(pres);
            PageConfig.ClearPageCache(pres);

            //Destroy the config
            //Shouldn't be connected to anything anymore
        }

        void ThisAddIn_DockPositionChanged(object sender, EventArgs e) {
            CustomTaskPane pane = (CustomTaskPane)sender;
            ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.TaskPaneDockPosition = (int)(pane.DockPosition);
            ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.Save();
        }

        void control_SizeChanged(object sender, EventArgs e) {
            UserControl control = (UserControl)sender;
            CustomTaskPane pane = (CustomTaskPane)(control.Tag);

            if(pane.Width != 0) {
                ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.TaskPaneWidth = pane.Width;
                ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.Save();
            }
            if(pane.Height != 0) {
                ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.TaskPaneHeight = pane.Height;
                ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.Save();
            }
        }

        void propControl_SizeChanged(object sender, EventArgs e) {
            UserControl control = (UserControl)sender;

            if (control.Width != 0) {
                ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.PropertiesPaneWidth = control.Width;
                ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.Save();
            }

            if (control.Height != 0) {
                ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.PropertiesPaneHeight = control.Height;
                ThreeByte.ActiveDeck.AddIn.Properties.Settings.Default.Save();
            }
        }

        void TaskPane_VisibleChanged(object sender, EventArgs e) {
            CustomTaskPane taskPane = (CustomTaskPane)sender;
            TaskPaneConfig[taskPane].Environment.ShowTaskPane = taskPane.Visible;
        }

        void PropertiesPane_VisibleChanged(object sender, EventArgs e) {
            CustomTaskPane taskPane = (CustomTaskPane)sender;
            try {
                CoreConfig config = GetCurrentConfig();
                config.Environment.ShowPropertiesPane = taskPane.Visible;
            } catch(Exception ex) {
                log.Error("Cannot get current config", ex);
            }
        }

        void Application_SlideSelectionChanged(PowerPoint.SlideRange SldRange) {
            //Get the current slide range;
            if (SldRange.Count != 1) {
                return;
            }
            
            //TODO: Better way to do this without referencing ActiveWindow?
            PowerPoint.DocumentWindow docWindow = this.Application.ActiveWindow;
            PowerPoint.Presentation pres = docWindow.Presentation;
            if(ConfigControls.ContainsKey(pres)) {
                ConfigControls[pres].LoadConfigForSlide(SldRange);
            }
        }

        public void PublishCurrentPresentation() {

            //Determine document window and context
            PowerPoint.DocumentWindow docWindow = this.Application.ActiveWindow;
            PowerPoint.Presentation pres = docWindow.Presentation;

            CoreConfig config = ConfigControls[pres].Config;

            //Check to see whether the presentation has a name & ID (and credentials)
            if(!(config.HasPresentationID) || !(config.HasPresenterLogin)) {
                NamePresentationForm newPresentationForm = new NamePresentationForm(config);
                DialogResult result = newPresentationForm.ShowDialog();
                if(result != DialogResult.OK) {
                    //Something went wrong so we shouldn't continue the publication
                    return;
                }
            }

            //TODO:Is there a better way to do this?
            //Check to see if there are any user slides to publish.
            bool containsSlides = false;
            foreach(SlideConfig s in config.Slides) {
                containsSlides = true;
                break;
            }
            if(!containsSlides) {
                MessageBox.Show("This presentation does not contain any configured activities to publish.  Please access the ActiveDeck task pane to create some user activities before publishing.", "Empty Presentation");
                return;
            }

            //Open UI Dialog with progress bar
            PublishForm publishDialog = new PublishForm(config);
            publishDialog.ShowDialog();

        }

        public void ClearAllUserDataForCurrentPresentation() {

            //Determine document window and context
            PowerPoint.DocumentWindow docWindow = this.Application.ActiveWindow;
            PowerPoint.Presentation pres = docWindow.Presentation;

            CoreConfig config = ConfigControls[pres].Config;

            //Check to see whether the presentation has a name & ID
            if(!(config.HasPresentationID) || !(config.HasPresenterLogin)) {
                MessageBox.Show("Please configure Presenter login credentials and register for a presentation ID first.", "Presentation Setup");
                return;
            }


            DialogResult result = MessageBox.Show("Are you sure that you want to erase all user data associated with this presentation?", "Delete User Data",MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation);
            if(result != DialogResult.Yes) {
                return;
            }

            try {
                WebServiceClient client = new WebServiceClient(config.PresenterLogin);
                client.ClearAllUserData(config.ID);
            } catch(Exception ex) {
                log.Warn("Problem clearing user data", ex);
                MessageBox.Show("There was a problem clearing user data: " + ex.Message);
            }
        }
       

        private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
        {
            log.Info("ActiveDeck AddIn Unloaded");
        }

        #region VSTO generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InternalStartup()
        {
            this.Startup += new System.EventHandler(ThisAddIn_Startup);
            this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
        }
        
        #endregion

        internal void DownloadCurrentUserData(string dataFormat) {
            log.Debug("Downloading UserData");

            PowerPoint.DocumentWindow docWindow = this.Application.ActiveWindow;
            PowerPoint.Presentation pres = docWindow.Presentation;

            CoreConfig config = ConfigControls[pres].Config;

            //Check to see whether the presentation has a name & ID
            if(!(config.HasPresentationID)) {
                NamePresentationForm newPresentationForm = new NamePresentationForm(config);
                DialogResult result = newPresentationForm.ShowDialog();
                if(result != DialogResult.OK) {
                    //Something went wrong so we shouldn't continue the publication
                    return;
                }
            }

            //Check to see whether the presentation has Presenter Login credentials
            if(!(config.HasPresenterLogin)) {
                //TODO: Prompt to enter user credentials
                throw new NotImplementedException("Need to enter user credentials");
            }

            //Write this to a location of the user's choice
            //TODO: specify xml extension
            System.Windows.Forms.SaveFileDialog fileDialog = new System.Windows.Forms.SaveFileDialog();
            fileDialog.OverwritePrompt = true;
            fileDialog.AddExtension = true;
            DateTime now = DateTime.Now;
            if (dataFormat == "XML")
            {
                fileDialog.Filter = @"XML files (*.xml)|*.xml|All files (*.*)|*.* ";
                fileDialog.FileName = string.Format("{0} UserData {1:0000}{2:00}{3:00}.xml", config.Name, now.Year, now.Month, now.Day);
            }
            else if (dataFormat == "XLS")
            {
                fileDialog.Filter = @"Excel files (*.xls, *.xlst, *.xlsx)|*.xls, *.xlst, *.xlsx|All files (*.*)|*.* ";
                fileDialog.FileName = string.Format("{0} UserData {1:0000}{2:00}{3:00}.xlsx", config.Name, now.Year, now.Month, now.Day);
            }

            System.Windows.Forms.DialogResult saveResult = fileDialog.ShowDialog();
            if(saveResult != System.Windows.Forms.DialogResult.OK) {
                return;
            }

            string filePath = fileDialog.FileName;


            //Gather the user data and aggregate it
            ReportGenerator reportGenerator = new ReportGenerator(config);
            try {
                if (dataFormat == "XML")
                {
                    log.Debug("Getting XML report");
                    XElement report = reportGenerator.GetXmlReport();
                    log.Debug("Report downloaded");
                    File.WriteAllText(filePath, report.ToString());
                }
                else if (dataFormat == "XLS")
                {
                    log.Debug("Getting XLS report");
                    reportGenerator.GetXlsReport(filePath);
                    log.Debug("Report downloaded");
                    //Cleanup
                    GC.Collect();
                    GC.WaitForPendingFinalizers();
                    GC.Collect();
                    GC.WaitForPendingFinalizers();
                }
            } catch(Exception ex) {
                log.Warn("Cannot retrieve user data report", ex);
            }


        }
    }
}
